package com.fzq.controller;


import com.fzq.config.MinioConfig;
import com.fzq.dao.ChunkUploadInfo;
import com.fzq.uilt.MinioUtils;
import com.google.common.net.HttpHeaders;
import io.minio.*;
import io.minio.errors.MinioException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@RestController
@RequestMapping("/minio")
public class MinioDemoController {

    @Autowired
    MinioClient minioClient;
    @Autowired
    MinioConfig minioConfig;
    @Autowired
    MinioUtils minioUtils;

    private Map<String, ChunkUploadInfo> chunkUploads = new ConcurrentHashMap<>();

    @PostMapping("/upload")
    public String uploadFile(MultipartFile file){
        try{
            //检查桶是否存在
            boolean bucketExists = minioClient.bucketExists(
                    BucketExistsArgs.builder()
                            .bucket(minioConfig.getBucketName())
                            .build()
            );

            if(!bucketExists){
                minioClient.makeBucket(
                        MakeBucketArgs.builder()
                                .bucket(minioConfig.getBucketName())
                                .build()
                );
            }

            String fileName =  generateFileName(file.getOriginalFilename());
            minioClient.putObject(PutObjectArgs.builder()
                            .bucket(minioConfig.getBucketName())
                            .object(fileName)
                            .stream(file.getInputStream(),file.getSize(),-1)
                            .contentType(file.getContentType())
                            .build());
            return minioUtils.generatePresignedUrl(fileName);
//            return minioUtils.generateDownloadUrl(fileName,"");
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException("文件上传失败: " + e.getMessage());
        }
    }


    /**
     * 生成唯一文件名
     * @param originalFilename
     * @return
     */
    private String generateFileName(String originalFilename) {
        LocalDateTime now = LocalDateTime.now();
        String datePath = now.getYear() + "/" +
                String.format("%02d", now.getMonthValue()) + "/" +
                String.format("%02d", now.getDayOfMonth()) + "/" +
                String.format("%02d", now.getHour()) + "/" +
                String.format("%02d", now.getMinute()) + "/";
        //简洁方法
//        LocalDateTime now = LocalDateTime.now();
//        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd/HH/mm/");
//        String datePath = now.format(formatter);

        String extension = "";
        if (originalFilename != null && originalFilename.contains(".")) {
            extension = originalFilename.substring(originalFilename.lastIndexOf("."));
        }

        return datePath + UUID.randomUUID() + extension;
    }


    /**
     * 文件下载
     * @param fileName
     * @return
     */
    @GetMapping("/downloadFile")
    public InputStream downloadFile(String fileName) {
        try {
            return minioClient.getObject(GetObjectArgs.builder()
                    .bucket(minioConfig.getBucketName())
                    .object(fileName)
                    .build());
        } catch (Exception e) {
            throw new RuntimeException("文件下载失败: " + e.getMessage());
        }
    }

    @GetMapping("/downloadFile2")
    public void downloadFile(String fileName, HttpServletResponse response) {
        // 使用try-with-resources确保GetObjectResponse会被正确关闭
        try (GetObjectResponse objectResponse = minioClient.getObject(GetObjectArgs.builder()
                .bucket(minioConfig.getBucketName())
                .object(fileName)
                .build())) {

            // 1. 设置响应头
            // 设置Content-Type，这里使用通用二进制流，或者根据文件类型设置具体值
            response.setContentType("application/octet-stream");
            // 强制浏览器下载文件并指定建议的文件名
            response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
                    "attachment; filename=\"" + fileName + "\"");

            // 2. 将MinIO的输入流复制到HTTP响应的输出流
            IOUtils.copy(objectResponse, response.getOutputStream());
            response.flushBuffer();

        } catch (Exception e) {
            // 3. 更合理的异常处理
            // 记录日志
            log.error("文件下载失败: {}", fileName, e);
            // 设置错误状态码
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            try {
                // 可以返回一个错误信息（如果是JSON API）
                response.getWriter().write("{\"error\": \"文件下载失败\"}");
            } catch (IOException ex) {
                log.error("写入错误响应失败", ex);
            }
        }
    }

    /**
     * 文件删除
     * @param fileName
     */
    @GetMapping("/deleteFile")
    public void deleteFile(String fileName) {
        try {
            minioClient.removeObject(RemoveObjectArgs.builder()
                    .bucket(minioConfig.getBucketName())
                    .object(fileName)
                    .build());
        } catch (Exception e) {
            throw new RuntimeException("文件删除失败: " + e.getMessage());
        }
    }


    /**
     * 文件是否存在
     * @param fileName
     * @return
     */
    @GetMapping("/fileExists")
    public boolean fileExists(String fileName) {
        try {
            minioClient.statObject(StatObjectArgs.builder()
                    .bucket(minioConfig.getBucketName())
                    .object(fileName)
                    .build());
            return true;
        } catch (Exception e) {
            return false;
        }
    }


    /**
     * 初始化分片
     * @param fileName
     * @param fileSize
     * @param chunkSize
     * @param bucketName
     * @return
     */
    @GetMapping("/initChunkUpload")
    public Map<String, Object> initChunkUpload(String fileName, Integer fileSize, Integer chunkSize, String bucketName) {
        try {
            String uploadId = UUID.randomUUID().toString();
            String targetBucket = bucketName != null ? bucketName : minioConfig.getBucketName();

            // 检查存储桶是否存在
            boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder()
                    .bucket(targetBucket)
                    .build());
            if (!bucketExists) {
                minioClient.makeBucket(MakeBucketArgs.builder()
                        .bucket(targetBucket)
                        .build());
            }

            // 计算分片数量
            int totalChunks = (int) Math.ceil((double) fileSize / chunkSize);

            // 创建分片上传信息
            ChunkUploadInfo uploadInfo = new ChunkUploadInfo();
            uploadInfo.setUploadId(uploadId);
            uploadInfo.setFileName(fileName);
            uploadInfo.setFileSize(fileSize);
            uploadInfo.setChunkSize(chunkSize);
            uploadInfo.setTotalChunks(totalChunks);
            uploadInfo.setBucketName(targetBucket);
            uploadInfo.setUploadedChunks(new HashSet<>());
            uploadInfo.setStartTime(System.currentTimeMillis());
            // 存储上传信息
            chunkUploads.put(uploadId, uploadInfo);

            Map<String, Object> result = new HashMap<>();
            result.put("uploadId", uploadId);
            result.put("fileName", fileName);
            result.put("fileSize", fileSize);
            result.put("chunkSize", chunkSize);
            result.put("totalChunks", totalChunks);
            result.put("bucketName", targetBucket);
            return result;
        } catch (Exception e) {
            log.error("初始化分片上传失败", e);
            throw new RuntimeException("初始化分片上传失败: " + e.getMessage());
        }
    }

    /**
     * 上传分片
     * @param uploadId
     * @param chunkNumber
     * @param chunk
     */
    @PostMapping("/uploadChunk")
    public void uploadChunk(String uploadId, Integer chunkNumber, MultipartFile chunk) {
        try {
            ChunkUploadInfo uploadInfo = chunkUploads.get(uploadId);
            if (uploadInfo == null) {
                throw new RuntimeException("分片上传任务不存在");
            }

            // 生成分片对象名
            String chunkObjectName = uploadId + "/chunk-" + chunkNumber;

            // 上传分片
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(uploadInfo.getBucketName())
                    .object(chunkObjectName)
                    .stream(chunk.getInputStream(), chunk.getSize(), -1)
                    .contentType(chunk.getContentType())
                    .build());

            // 记录已上传的分片
            uploadInfo.getUploadedChunks().add(chunkNumber);

            log.info("分片上传成功: uploadId={}, chunkNumber={}", uploadId, chunkNumber);
        } catch (Exception e) {
            log.error("分片上传失败", e);
            throw new RuntimeException("分片上传失败: " + e.getMessage());
        }
    }

    /**
     * 完成分片上传
     * @param uploadId
     * @param fileName
     * @return
     */
    @GetMapping("/completeChunkUpload")
    public String completeChunkUpload(String uploadId, String fileName) {
        try {
            ChunkUploadInfo uploadInfo = chunkUploads.get(uploadId);
            if (uploadInfo == null) {
                throw new RuntimeException("分片上传任务不存在");
            }

            // 检查是否所有分片都已上传
            if (uploadInfo.getUploadedChunks().size() != uploadInfo.getTotalChunks()) {
                throw new RuntimeException("还有分片未上传完成");
            }

            // 生成最终文件名
            String finalFileName = generateFileName(fileName);

            // 合并分片（这里简化处理，实际应该使用MinIO的ComposeObject）
            // 由于MinIO的ComposeObject需要所有对象在同一存储桶中，我们这里使用临时方案
            // 在实际生产环境中，建议使用MinIO的multipart upload API

            // 创建合并后的文件对象
            ByteArrayOutputStream mergedStream = new ByteArrayOutputStream();
            for (int i = 1; i <= uploadInfo.getTotalChunks(); i++) {
                String chunkObjectName = uploadId + "/chunk-" + i;
                try (InputStream chunkStream = minioClient.getObject(GetObjectArgs.builder()
                        .bucket(uploadInfo.getBucketName())
                        .object(chunkObjectName)
                        .build())) {
                    byte[] buffer = new byte[8192];
                    int bytesRead;
                    while ((bytesRead = chunkStream.read(buffer)) != -1) {
                        mergedStream.write(buffer, 0, bytesRead);
                    }
                }

                // 删除分片文件
                minioClient.removeObject(RemoveObjectArgs.builder()
                        .bucket(uploadInfo.getBucketName())
                        .object(chunkObjectName)
                        .build());
            }

            // 上传合并后的文件
            byte[] mergedData = mergedStream.toByteArray();
            try (ByteArrayInputStream inputStream = new ByteArrayInputStream(mergedData)) {
                minioClient.putObject(PutObjectArgs.builder()
                        .bucket(uploadInfo.getBucketName())
                        .object(finalFileName)
                        .stream(inputStream, mergedData.length, -1)
                        .contentType("application/octet-stream")
                        .build());
            }

            // 清理上传信息
            chunkUploads.remove(uploadId);
            return minioUtils.getFileUrl(finalFileName);
        } catch (Exception e) {
            log.error("完成分片上传失败", e);
            throw new RuntimeException("完成分片上传失败: " + e.getMessage());
        }
    }

    /**
     * 获取上传状态
     * @param uploadId
     * @return
     */
    public Map<String, Object> getChunkUploadStatus(String uploadId) {
        ChunkUploadInfo uploadInfo = chunkUploads.get(uploadId);
        if (uploadInfo == null) {
            throw new RuntimeException("分片上传任务不存在");
        }

        Map<String, Object> status = new HashMap<>();
        status.put("uploadId", uploadInfo.getUploadId());
        status.put("fileName", uploadInfo.getFileName());
        status.put("fileSize", uploadInfo.getFileSize());
        status.put("chunkSize", uploadInfo.getChunkSize());
        status.put("totalChunks", uploadInfo.getTotalChunks());
        status.put("uploadedChunks", uploadInfo.getUploadedChunks().size());
        status.put("progress", (double) uploadInfo.getUploadedChunks().size() / uploadInfo.getTotalChunks() * 100);
        status.put("startTime", uploadInfo.getStartTime());
        status.put("bucketName", uploadInfo.getBucketName());

        return status;
    }



    // 合并分片
    @GetMapping("/mergeChunks")
    public void mergeChunks(String uploadId, String targetObjectName) {
        try {
            ChunkUploadInfo uploadInfo = chunkUploads.get(uploadId);
            if (uploadInfo == null) {
                throw new RuntimeException("分片上传任务不存在");
            }

            // 检查是否所有分片都已上传
            if (uploadInfo.getUploadedChunks().size() != uploadInfo.getTotalChunks()) {
                throw new RuntimeException("还有分片未上传完成");
            }
            // 准备分片源列表
            List<ComposeSource> sourceObjects = new ArrayList<>();
            for (int i = 1; i <= uploadInfo.getTotalChunks(); i++) {
                String chunkObjectName = uploadId + "/chunk-" + i;
                sourceObjects.add(
                        ComposeSource.builder()
                                .bucket(uploadInfo.getBucketName())
                                .object(chunkObjectName)
                                .build()
                );
            }

            // 使用MinIO的composeObject API合并分片
            ObjectWriteResponse response = minioClient.composeObject(
                    ComposeObjectArgs.builder()
                            .bucket(uploadInfo.getBucketName())
                            .object(targetObjectName) // 合并后的目标对象名
                            .sources(sourceObjects)
                            .build()
            );

            log.info("分片合并成功: uploadId={}, 目标对象={}", uploadId, targetObjectName);

            // 可选：合并成功后删除分片对象
            deleteChunkObjects(uploadId, uploadInfo);

        } catch (Exception e) {
            log.error("分片合并失败", e);
            throw new RuntimeException("分片合并失败: " + e.getMessage());
        }
    }

    // 删除分片对象（可选）
    private void deleteChunkObjects(String uploadId, ChunkUploadInfo uploadInfo) {
        try {
            for (int i = 1; i <= uploadInfo.getTotalChunks(); i++) {
                String chunkObjectName = uploadId + "/chunk-" + i;
                minioClient.removeObject(
                        RemoveObjectArgs.builder()
                                .bucket(uploadInfo.getBucketName())
                                .object(chunkObjectName)
                                .build()
                );
            }
            log.info("已清理分片对象: uploadId={}", uploadId);
        } catch (Exception e) {
            log.warn("清理分片对象失败: uploadId={}", uploadId, e);
            // 这里可以选择记录日志但不抛出异常，避免影响主流程
        }
    }

    /**
     * 删除创建的桶
     * @param bucketName
     */
    @GetMapping("/deleteBucket")
    public void deleteBucket(String bucketName){
        try {
            // 确保存储桶为空
            // 如果需要，可以先删除存储桶中的所有对象

            // 删除存储桶
            minioClient.removeBucket(
                    RemoveBucketArgs.builder()
                            .bucket(bucketName)
                            .build()
            );
            System.out.println("存储桶删除成功");
        } catch (MinioException e) {
            System.err.println("MinIO错误: " + e.getMessage());
        } catch (Exception e) {
            System.err.println("错误: " + e.getMessage());
        }
    }
}
